package application

import (
	"flag"
	"gitee.com/kristas/booting-go/framework/banners"
	"gitee.com/kristas/booting-go/framework/common/util/properties"
	"gitee.com/kristas/booting-go/framework/constant"
	"gitee.com/kristas/booting-go/framework/core/configure"
	"gitee.com/kristas/booting-go/framework/core/ioc/bean_factory"
	"gitee.com/kristas/booting-go/framework/core/log"
	"gitee.com/kristas/booting-go/framework/core/statement"
	"gitee.com/kristas/booting-go/framework/core/statement/types"
	"gopkg.in/yaml.v3"
	"os"
	"os/signal"
	"sort"
	"strings"
	"syscall"
	"time"
)

var (
	Environment string
	port        int
	argConfig   string
)

func init() {
	initFlag()
}

func initFlag() {
	flag.StringVar(&Environment, "env", "local", "local,dev,test,prod...")
	flag.IntVar(&port, "p", -1, "the port your server listen at.")
	flag.StringVar(&argConfig, "c", "", "specify configs")
	flag.Parse()
}

type Application struct {
	sc        chan os.Signal
	Log       log.Logger `wire:""`
	Server    *ServerConfig
	StartTime time.Time
	genMode   bool
}

func NewApplication() *Application {
	return &Application{
		sc:        make(chan os.Signal, 1),
		StartTime: time.Now(),
	}
}

type ServerConfig struct {
	types.AutoConfigure `prefix:"server"`
	Port                int `yaml:"port"`
}

func (r *Application) Run(args ...string) {
	err := r.init()
	if err != nil {
		panic(err)
	}
	r.Log.Info(banners.Banners, constant.Version)
	r.Log.Info("active environment: %s", Environment)
	err = bean_factory.Init()
	if err != nil {
		r.Log.Fatal("bean factory init failed: %s", err)
	}
	r.afterRefresh()
	r.Log.Info("framework boot up success, cost %v", time.Since(r.StartTime))

	if r.genMode {
		return
	}
	signal.Notify(r.sc, os.Kill, os.Interrupt,
		syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
	sig := <-r.sc
	err = r.Shutdown(sig)
	if err != nil {
		panic(err)
	}
	r.Log.Info("framework shutdown by %s, running %s", sig, time.Since(r.StartTime))
}

func (r *Application) GenMode() *Application {
	r.genMode = true
	return r
}

func (r *Application) init() error {
	// init config
	r.initConfig()
	// init log
	loggerFactory := log.NewLoggerFactory()
	err := bean_factory.BindProperties(loggerFactory)
	if err != nil {
		return err
	}
	logger, err := loggerFactory.GetLogger()
	if err != nil {
		return err
	}
	bean_factory.GetApplicationContext().RegisterBeanWithName("log", logger)

	err = bean_factory.BindProperties(r)
	if err != nil {
		return err
	}
	return nil
}

func (r *Application) initConfig() {
	configure.Init(Environment)
	props := make(map[string]interface{})
	if port != -1 {
		r.Server.Port = port
		props["server.port"] = port
	}
	if argConfig != "" {
		argConfig = strings.ReplaceAll(argConfig, ",", "\n")
		err := properties.Unmarshal([]byte(argConfig), &props)
		if err != nil {
			panic(err)
		}
	}
	out, _ := yaml.Marshal(properties.PropMapExpand(props))
	configure.SetConfig(out)
	configure.CompileVariable()
}

func (r *Application) Shutdown(sig os.Signal) error {
	err := bean_factory.Destroy()
	if err != nil {
		return err
	}
	return nil
}

func (r *Application) afterRefresh() {
	callRunners()
}

func callRunners() {
	components := bean_factory.GetApplicationContext().GetAllBeansByInterface(new(statement.ApplicationRunner))
	if len(components) == 0 {
		return
	}
	var runners []statement.ApplicationRunner
	for i := range components {
		runners = append(runners, components[i].(statement.ApplicationRunner))
	}
	sort.Slice(runners, func(i, j int) bool {
		return runners[i].Order() < runners[j].Order()
	})
	for i := range runners {
		err := runners[i].Run()
		if err != nil {
			panic(err)
		}
	}
}
